﻿using System;

using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Diagnostics;
using Silvermoon.Controls.Base;

namespace Silvermoon.Controls.Beta
{
    /// <summary>
    /// Base class for a control that displays <see cref="ItemBase"/> in fixed rows with one or more columns  
    /// and can be used as an Item for a <see cref="ListBoxBase"/> control.
    /// </summary>
    public abstract class ItemGroupBase : ItemBase, IItemControl
    {
        #region fields

        protected ListBoxContainer Container;

        protected int TitleHeight { get { return !string.IsNullOrEmpty(Title) ? 64 : 0; } }
        private string title;
        private GroupItemCollection items;
        protected int ClipTop;
        protected int? ClipHeight;
        protected ItemGroupAnimator animator;
        private int columnCount = 1;
        int oldTopIndex = 0;
        int oldBottomIndex = -1;


        #endregion
        #region ctor

        /// <summary>
        /// Initializes a new instance of the <see cref="ItemGroup"/> class.
        /// </summary>
        public ItemGroupBase()
            : base()
        {
            animator = CreateAnimator();
            items = new GroupItemCollection(this);
            Container = new ListBoxContainer();
            //Container.ChildControls.Parent = this;
            ItemHeight = 80;
            VisibleControls.AddLast(Container);

            // hit test must be disabled, otherwise ListBoxBase would falsy recognize <see cref="ListBoxBase"/> as item to be taped instead of the item inside:
            //  this.ControlFlags = (ControlFlags | ControlFlags.IgnoreClipping) & ~ControlFlags.HitTestVisible;

        }

        protected override void Dispose(bool dispose)
        {
            if (items != null && !items.IsVirtual)
            {
                foreach (var item in items)
                {
                    if (item != null) item.Dispose();
                }
                Container.VisibleControls.Clear();
            }
           // Container.VisibleControls.ClearAndDispose(); // since items are previously added, they must be removed before disposing, otherwise they would be disposed again.
            base.Dispose(dispose);
        }

        #endregion
        #region props

        /// <summary>
        /// Gets or sets the height for each <see cref="ItemBase"/>.
        /// </summary>
        public int ItemHeight { get; set; }

        public int ItemWidth
        {
            get { return Container.Width / columnCount; }
        }

        /// <summary>
        /// Gets the total required height for all items.
        /// </summary>
        public int TotalHeight
        {
            get
            {
                int cnt = items.Count;
                if (columnCount > 0) cnt = (cnt + columnCount - 1) / columnCount;
                int h = ItemHeight * cnt;
                if (animator.IsAnimated)
                {
                    h = animator.AdjustHeight(h);
                }
                return h;
            }
        }

        /// <summary>
        /// Gets or sets the number of columns for the group.
        /// </summary>
        public int ColumnCount
        {
            get { return columnCount; }
            set
            {
                if (value < 1) throw new ArgumentOutOfRangeException("Columns");
                columnCount = value;
                Invalidate(InvalidationFlag.LayoutAppearance);
            }
        }


        /// <summary>
        /// Gets or sets the number of items for this group.
        /// </summary>
        public int Count
        {
            get { return items.Count; }
            set
            {
                items.Count = value;
            }
        }

        /// <summary>
        /// Gets the items for the group.
        /// </summary>
        public GroupItemCollection Items { get { return items; } }

        /// <summary>
        /// Gets or sets the group title.
        /// </summary>
        public string Title
        {
            get { return title ?? string.Empty; }
            set
            {
                if (title != value)
                {
                    title = value;
                    Invalidate(InvalidationFlag.Text);
                }
            }
        }


        #endregion
        #region methods

        /// <summary>
        /// Creates the animator to perform insert and remove animations as well as maintain positions whilst in animation.
        /// </summary>
        /// <returns>Instance of <see cref="SecitonItemAnimator"/></returns>
        protected virtual ItemGroupAnimator CreateAnimator()
        {
            return new ItemGroupAnimator(this);
        }

        protected virtual ItemBase GetVirtualItem(int index)
        {
            ListBoxBase listBox = ListBox;
            ItemBase item = listBox.GetVirtualItem(this, index);
            if (item != null) item.IsSelected = listBox.IsItemSelected(item);
            return item;
        }

        protected override void OnSizeChanged()
        {
            if (items != null) SetHeight();
            base.OnSizeChanged();
        }

        protected override void OnLayout()
        {
            if (CheckFlag(InvalidationFlag.Size | InvalidationFlag.Layout))
            {
                Container.Bounds = new Rectangle(0, TitleHeight - 0, Width, Height - GetTitleHeight());
                LayoutItems((InvalidationMask & InvalidationFlag.UpdateChildren) == 0);
            }
            if (CheckFlag(InvalidationFlag.UpdateChildren))
            {
                UpdateItemProperties();
            }
        }

        private void UpdateItemProperties()
        {
            Control c = Container.VisibleControls.First;
            while (c != null)
            {
                ItemBase item = c as ItemBase;
                if (item != null) item.OnUpdateProperties();
                c = c.NextControl;
            }
        }

        public void SetClip(int top, int? height)
        {
            if (height.HasValue && height.Value < 0) height = 0;
            ClipTop = top;
            ClipHeight = height;
            Invalidate(InvalidationFlag.Layout);
        }


        /// <summary>
        /// Gets the index of an item under a position.
        /// </summary>
        /// <param name="position">Position of the item.</param>
        /// <returns>Index of the item.</returns>
        protected virtual int GetFirstItemIndexAt(int position)
        {
            int ih = ItemHeight;
            position *= columnCount;
            int maxHeight = items.Count * ih - 1;
            if (position > maxHeight) position = maxHeight;
            if (position < 0) position = 0;

            int index = position / ih;

            return index;
        }

        public virtual int GetLastItemIndexAt(int position)
        {
            int index = GetFirstItemIndexAt(position) + columnCount - 1 + columnCount * 2;
            int count = items.Count;
            index = count - 1;  //debug
            if (index >= count) index = count - 1;
            return index;
        }


        protected virtual void LayoutItems(bool notifyItems)
        {
            bool canDetach = ListBox.CanDetach;

            int t = ClipTop;
            if (t > 0) t -= GetTitleHeight();
            int topIndex = GetFirstItemIndexAt(t);
            int ch = ClipHeight ?? Container.Height;

            ControlCollection childControls = Container.VisibleControls;
            childControls.Clear();

            int h = ItemHeight;
            int w = ItemWidth;

            int i = topIndex;
            for (; i < Count; i++)
            {
                int col = i % columnCount;
                int y = (i / columnCount) * ItemHeight;
                int x = w * col;
                Rectangle bounds = new Rectangle(x, y, w, h);
                ItemBase item = items.GetItemOrNull(i);
                if (animator.AdjustBounds(item, i, ref bounds)) canDetach = false;
                if (bounds.Top >= ch) break;
                if (item == null) item = items.CreateVirtualItem(i);
                item.Bounds = bounds;
                item.ItemIndex = i;
                childControls.AddLast(item);

                if (notifyItems && (i < oldTopIndex || i >= oldBottomIndex)) item.OnUpdateProperties();
            }
            if (canDetach) DetachItems(topIndex, i);
            oldTopIndex = topIndex;
            oldBottomIndex = i;
        }


        private void DetachItems(int topIndex, int bottomIndex)
        {
            if (Container.VisibleControls != null)
            {
                int max = Math.Min(items.Count - 1, oldBottomIndex);
                for (int i = oldTopIndex; i <= max; i++)
                {
                    ItemBase item = items[i];
                    if ((item.Parent == null) && CanDetachItem(item))
                    {
                        // note that an item which has parent assigned thus cannot be detached.
                        items.Zero(i);
                    }
                }
            }
        }

        /// <summary>
        /// Forces all realized item controls to be detached.
        /// </summary>
        public void DetachItems()
        {
            // DetachItems(oldTopIndex, oldBottomIndex);
            ItemBase item = Container.VisibleControls.First as ItemBase;
            while (item != null)
            {
                if (CanDetachItem(item))
                {
                    items.Zero(item.ItemIndex);
                }
                item = item.NextControl as ItemBase;
            }
            oldBottomIndex = -1;
            Invalidate(InvalidationFlag.Layout);
        }

        protected virtual bool CanDetachItem(ItemBase item)
        {
            if (item.IsSelected) return false;
            return ListBox.OnCanDetachItem(this, item);
        }

        internal void InvalidateItems()
        {
            SetHeight();
            Invalidate(InvalidationFlag.Layout);
        }

        #region IItemOwner Members

        void IItemControl.ItemsInvalidated()
        {
            SetHeight();
            Invalidate(InvalidationFlag.Layout);
        }


        private void SetHeight()
        {
            Height = TotalHeight + GetTitleHeight();
            ListBoxBase lb = ListBox;
            if (lb != null) lb.ItemHeightChanged(this);
        }

        protected virtual int GetTitleHeight()
        {
            return TitleHeight;
        }


        /// <summary>
        /// Gets an instance of <see cref="ItemBase"/>.
        /// </summary>
        ItemBase IItemControl.GetVirtualItem(int index)
        {
            return GetVirtualItem(index);
        }


        void IItemControl.OnItemRemoved(int index, ItemBase item)
        {
            animator.UpdateRemoveAnimations(index);
            ListBoxBase lb = ListBox;
            if (lb != null) lb.OnItemRemoved(item, this);
        }

        void IItemControl.OnItemInserted(int index, ItemBase item)
        {
            animator.UpdateInsertAnimations(index);
        }

        void IItemControl.OnClear()
        {
            ListBoxBase listBox = ListBox;
            foreach (var item in items)
            {
                listBox.OnItemRemoved(item, this);
            }
            animator.OnClear();
        }

        #endregion

        /// <summary>
        /// Adds an item with aasynchrone animation.
        /// </summary>
        /// <param name="item">Item to add.</param>
        public void AddAnimated(ItemBase item)
        {
            animator.AddAnimated(item, 0);
        }

        /// <summary>
        /// Inserts an item with asynchrone animation.
        /// </summary>
        /// <param name="index">Index to insert.</param>
        /// <param name="item">Item to insert.</param>
        public void InsertAnimated(int index, ItemBase item)
        {
            animator.InsertAnimated(index, item, 0);
        }

        public void AddAnimated(ItemBase item, int delay)
        {
            animator.AddAnimated(item, delay);
        }

        public void InsertAnimated(int index, ItemBase item, int delay)
        {
            animator.InsertAnimated(index, item, delay);
        }

        /// <summary>
        /// Removes an item with asynchrone animation.
        /// </summary>
        /// <param name="item">Item to remove.</param>
        public ItemBase RemoveAnimated(ItemBase item)
        {
            animator.RemoveAnimated(item);
            return item;
        }

        /// <summary>
        /// Removes an item with asynchrone animation.
        /// </summary>
        /// <param name="index">Index of item to remove.</param>
        public ItemBase RemoveAnimated(int index)
        {
            ItemBase item = items[index];
            animator.RemoveAnimated(index);
            return item;
        }

        /// <summary>
        /// Notifies an item to update it's properties. This method usually occurs when it gets visible inside a <see cref="ListBox"/> or
        /// when the listbox has changed properties that also affect items inside.
        /// </summary>
        public override void OnUpdateProperties()
        {
            UpdateItemProperties();
            base.OnUpdateProperties();
        }

        protected internal override void OnTouchDown(TouchEventArgs e)
        {
            e.Handled = true;
            base.OnTouchDown(e);
        }

        public override void BringIntoView(Rectangle bounds, bool animated)
        {
            if (ListBox != null)
            {
                bounds.Offset(X, Y-GetTitleHeight());
                ListBox.BringRectIntoView(bounds, animated);
            }
            else
            {
                base.BringIntoView(bounds, animated);
            }
        }

        /// <summary>
        /// Brings the specified item into view.
        /// </summary>
        /// <param name="item">Item to bring into view. Must be member of Items.</param>
        /// <param name="animated">True to perform animation, otherwise false.</param>
        public void BringItemIntoView(ItemBase item, bool animated)
        {
            if (Container != null)
            {
                Rectangle bounds = item.Bounds;
                // bounds.Offset(X, Y);
                Container.BringIntoView(bounds, animated);
            }
        }


        #endregion

    }
}
